require 'apr.net.rawsocket'
require 'apr.oo'
local apr, setmetatable, table, ipairs, pcall, error =
      apr, setmetatable, table, ipairs, pcall, error
---apr.net wraps around the apr_network_io socket libraries to support full networking support
-- the apr.net module itself provides the raw client/server socket and DNS helper functions
module('apr.net')

--- Factory function for creating client sockets
-- @param host the IP address or domain to connect to
-- @param port the TCP port to connect to
-- @param timeout how long to wait before timing out the connection (in seconds)
function connect(host, port, timeout)
    return Client.new(host, port, timeout)
end

--- Constructor for client socket objects
-- @param host the IP address or domain to connect to
-- @param port the TCP port to connect to
-- @param timeout how long to wait before timing out (in seconds) - defaults to 30 seconds
Client = apr.oo.Class(function(self, host, port, timeout, raw)
    timeout = timeout or 30
    if raw == nil then
        raw, errormsg = apr.net.rawsocket.connect(dnsresolve(host, true), port)
    end
    
    if raw == nil then
        return nil, errormsg
    end
    
    self.timeout = timeout; self.port = port; self.host = host; self.raw = raw
end)

--- Send data along the socket
-- @param data the datastring to pass along the socket
function Client:write(...)
    apr.net.rawsocket.send(self.raw, ...)
end

--- Receive data along a socket
-- @return the datastring received from the socket
function Client:read(...)
    return apr.net.rawsocket.receive(self.raw, ...)
end

--- Close out the socket and delete the internal struct
function Client:close()
    apr.net.rawsocket.close(self.raw)
    self.raw = nil
end

function create_server(port, handle_func, error_func, interface)
    return Server.new(port, handle_func, error_func, interface)
end

Server = apr.oo.Class(function(self, port, handle_func, error_func, interface)
    interface = interface or '0.0.0.0'
    self.port = port; self.handle_func = handle_func; self.error_func = error_func; self.interface = interface; self.raw = nil
end)

function Server:start(backlog)
    if self.raw ~= nil then
        return nil
    end
    
    backlog = backlog or 32
    self.raw = apr.net.rawsocket.newsocket()
    apr.net.rawsocket.bind(self.raw, self.port, self.interface)
    apr.net.rawsocket.listen(self.raw, backlog)
end

function Server:stop()
    if self.raw ~= nil then
        apr.net.rawsocket.close(self.raw)
    end
    
    self.raw = nil
end

function Server:accept()
    local client_raw = apr.net.rawsocket.accept(self.raw)
    return Client.new(self.interface, self.port, nil, client_raw)
end

function Server:handle_request()
    local client = self:accept()
    local success, errstr = pcall(self.handle_func, client)
    if not success then
        if self.error_func == nil then
            error(errstr)
        else
            self.error_func(client, errstr)
        end
    end
    client:close()
end

function Server:serve()
    while true do
        self:handle_request()
    end
end

--- If given an IP address, return the same IP address as a table of one value.  If given a domain name, 
-- resolve the domain to a list of IP addresses and return the table
-- @param host the IP address or domain name to resolve
-- @param return_single set to true to return the first IP address as a string instead of a table
-- @return a table of the matching IP addresses or nil and an error message if there is a problem
function dnsresolve(host, return_single)
    local addrs, errormsg = apr.net.rawsocket.dnsresolve(host)
    if addrs == nil then
        return nil, errormsg
    end
    
    if return_single then
        return addrs[1]
    else
        return addrs
    end
end
